classdef myDotNetDateTime < handle
  % MATLAB .NET System.DateTime class w/ improved DST rule
  %
  % Author: David Goldsmith, Wash. State Dept. of Ecology, dgol461@ecy.wa.gov
  % Release date: N/A
  
    properties (Constant)
        MS2MATLAB_DTOFFSET = 693960; % Difference between MATLAB's
                                     % and Microsoft's sdn's, i.e.,
                                     % MATLAB = MS + MS2MATLAB_DTOFFSET
        isDateTime = @myDotNetDateTime.IsDateTime;
        toMATLAB   = @myDotNetDateTime.MS2MATLAB;
    end
    
    properties
        data = {}; % cell array of MS .NET System.DateTime objects
        mask = []; % Binary array, 1 (resp. 0) where data is (resp. not)
                   % a valid .NET DateTime value
    end
    
    properties (Dependent)
        MLBsdns = []; % data as MATLAB Serial Date Numbers
        isDST   = []; % Daylight Savings Time (US) function that works 
                      % prior to 2007
    end
    
    methods
        
        function DT = myDotNetDateTime(Data) % Constructor
            if ~exist('System.DateTime', 'class')
                try
                    import System.DateTime.*; 
                catch ME
                    DT.data = {['Sorry, unable to create myDotNetDateTime '...
                               'instance due to System.DateTime import '...
                               'failure with message ' ME.message '.  ']};
                    return
                end
            end
            if nargin
                if isnumeric(Data), Data = datestr(Data); end
                if ischar(Data), Data = cellstr(Data); end 
                if iscellstr(Data)
                    [DT.mask, DT.data] = DT.isDateTime(Data);
                    if ~any(DT.mask) % If no conversions to .NET DateTimes 
                                     % were successful
                        DT.data = {['Sorry, unable to create myDotNetDateTime '...
                                    'instance: none of the supplied datetimes '...
                                    'successfully converted to an MS .NET '...
                                    'System.DateTime object.  ']};
                      % No need to set DT.mask in this case, as DT.isDateTime 
                      % will have returned the correct result
                    end
                else
                    DT.data = {['Sorry, unable to create myDotNetDateTime '...
                                'instance: input data type not supported '...
                                '(only numeric, char, and cellstr types '...
                                'supported).  ']};
                end
            end
        end
        
        function sdns = get.MLBsdns(obj)
            if isempty(obj.data) || ~any(obj.mask)
                sdns = [];
            else
                sdns = obj.toMATLAB(obj.data);
            end
        end
        
        function b = get.isDST(obj)
            saveSize = size(obj.data);
            N = numel(obj.data);
            tmp = reshape(obj.data, N, 1);
            b = zeros(N,1); 
            for n=1:N
                Y = tmp{n}.Year; 
                if Y > 2006
                    b(n) = tmp{n}.IsDaylightSavingTime();
                elseif Y > 1917
                    M = tmp{n}.Month;
                    switch 1
                        case (M > 4) && (M < 10)
                            b(n) = 1;
                        case M==4
                          % Find first Sunday in April
                            for i=1:7
                                ttmp = System.DateTime(Y,4,i);
                                if ttmp.DayOfWeek().int32
                                    continue;                                    
                                else
                                    break; % 0 = Sunday
                                end
                            end
                            atmp = ttmp.DayOfYear();
                            btmp = tmp{n}.DayOfYear();
                            if (btmp>atmp) || (atmp==btmp && tmp{n}.Hour>2)
                               b(n) = 1; 
                            end                            
                        case M==10
                          % Find last Sunday in October
                            for i=25:31
                                ttmp = System.DateTime(Y,10,i);
                                if ttmp.DayOfWeek().int32
                                    continue;                                    
                                else
                                    break; % 0 = Sunday
                                end
                            end
                            atmp = ttmp.DayOfYear();
                            btmp = tmp{n}.DayOfYear();
                            if (btmp<atmp) || (atmp==btmp && tmp{n}.Hour<2)
                               b(n) = 1; 
                            end                            
                    end
                end
            end
            b = reshape(b, saveSize);
        end
        
    end
    
    methods(Static)

        function [b, inputAsMSDT] = IsDateTime(DT)
          % DT is one or more datetimes as string(s): if just one, it can be
          % either a string (char array) or a single-element cellstr array
          % (to which a single string is converted before processing); if
          % more than one, it must be a cellstr array.
          %
          % This function uses System.DateTime.TryParse to make its deter-
          % mination: where TryParse is successful, b = 1 (true) and 
          % inputAsMSDT is the resulting System.DateTime object; where it
          % fails, b = 0 and inputAsMSDT is the zero value of System.DateTime 
          
            if ischar(DT), DT = {DT}; end
            saveSize = size(DT);
            N = numel(DT);
            DT = reshape(DT, N, 1);
            b = ones(N,1); 
            inputAsMSDT = cell(N,1);
            for n=1:N
                [b(n), inputAsMSDT{n}] = System.DateTime.TryParse(DT{n});
            end
            b = reshape(b, saveSize);
            inputAsMSDT = reshape(inputAsMSDT, saveSize);
        end

        function out = MS2MATLAB(in)
            out = cellfun(@ToOADate, in) + myDotNetDateTime.MS2MATLAB_DTOFFSET;
        end
        
        function b = IsDST(DT)
            temp = myDotNetDateTime(DT);
            b = temp.isDST;
        end
        
    end
    
end